package contegor.scire.core.queries;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import contegor.scire.core.Concept;
import contegor.scire.core.Connectable;
import contegor.scire.core.Container;
import contegor.scire.core.Id;
import contegor.scire.core.IdFragment;
import contegor.scire.core.InfiniteSingletonList;
import contegor.scire.core.Link;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.ReturnPointAdapter;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.Version;
import contegor.scire.core.WithConsequences;

/**
 * Given {@link Query} that return {@link Link}s, this one returns the {@link Connectable} target of them.
 *
 * @version $Revision: 1.4 $
 * @author  Héctor Cirbián Pousa ($Author: hector $)
 */
@Version("$Id: GetLinksTargets.java,v 1.4 2010-02-01 10:55:41 hector Exp $")
public class GetTargetsFromLinks extends QueryWithDependencies<Link, Concept> implements WithConsequences<Concept, Concept> {
  private static final long serialVersionUID = 3212123904356533452L;

  private ConcurrentHashMap<Id, List<IdFragment>> idsMap;
  private List<GetConceptsExpanded> consequences;
  private ReturnPoint<Concept> consecuencesReturnPoint = new ReturnPointAdapter<Concept>() {
    @Override
    public <Y extends Concept> void manage(TaskId taskId, Y result) {
      GetTargetsFromLinks.this.returnPoint.manage(taskId, result);
    }
    @Override
    public <Y extends Concept> void manage(TaskId taskId, Collection<Y> result) {
      GetTargetsFromLinks.this.returnPoint.manage(taskId, result);
    }
    @Override
    public void manage(TaskId taskId, Exception exception) {
      GetTargetsFromLinks.this.returnPoint.manage(taskId, exception);
    }
  };

  private ReturnPoint<Link>  dependenciesReturnPoint = new ReturnPointAdapter<Link>() {
    public void manage(TaskId taskId, Link result) {
      Id targetId          = result.getTarget(); 
      Id targetContainerId = targetId.getParentId();
      List<IdFragment> ids = idsMap.get(targetContainerId);
      if (ids == null) {
        ids = Collections.synchronizedList(new ArrayList<IdFragment>());
        List<IdFragment> prev = idsMap.putIfAbsent(targetContainerId, ids);
        if (prev != null)
          ids = prev;
      }
      ids.add(targetId.getLastIdFragment());
    }
    public void manage(TaskId taskId, Exception exception) {
      returnPoint.manage(taskId, exception);
    }
  };

  public GetTargetsFromLinks(Query<Link> dependencie) {
    super(dependencie);
    this.idsMap = new ConcurrentHashMap<Id, List<IdFragment>>();
  }

  public GetTargetsFromLinks(Id targetContainerId, Query<Link> ... dependencies) {
    super(targetContainerId, dependencies);
    this.idsMap = new ConcurrentHashMap<Id, List<IdFragment>>();
  }

  public void process(Container container) {
    consequences = new ArrayList<GetConceptsExpanded>(idsMap.size());
    TaskId subTaskId = this.taskId;
    for (Entry<Id,List<IdFragment>> entry: idsMap.entrySet()) {
      IdFragment[] idFragments = entry.getValue().toArray(new IdFragment[entry.getValue().size()]);
      consequences.add(new GetConceptsExpanded(subTaskId = subTaskId.next(subTaskId == this.taskId), entry.getKey(), idFragments));
    }
  }

  @Override
  public List<? extends Task<Link>> getDependencies() {
    return this.dependencies;
  }

  @Override
  public List<? extends ReturnPoint<Link>> getDependenciesReturnPoints() {
    if (this.dependencies == null)
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<Link>>(this.dependenciesReturnPoint, this.dependencies.size());
  }

  @Override
  public List<? extends Task<Concept>> getConsequences() {
    return this.consequences;
  }

  @Override
  public List<? extends ReturnPoint<Concept>> getConsequencesReturnPoints() {
    if (this.consequences == null)
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<Concept>>(this.consecuencesReturnPoint, this.consequences.size());
  }
}
